package com.zgjl.api.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;

/**
 * HmacUtil
 *
 * HMAC 签名工具类（支持 JSON canonicalization）
 * 用途：
 * 1. 对 JSON 数据进行 HMAC-SHA256 签名
 * 2. 支持 JSON key 按升序排序，保证字段顺序不同也能验签一致
 * 3. Base64 编码输出签名，便于传输
 */
public class HmacUtil {

    private static final ObjectMapper mapper = new ObjectMapper();

    private static long timeWindowMs;

    // 设置静态密钥
    public static void setTimeWindowMs(long timeWindowMs) {
        HmacUtil.timeWindowMs = timeWindowMs;
    }

    /**
     * 将 JSON 转成 TreeMap 按 key 升序，再输出标准化 JSON
     * 解决 HMAC 对字段顺序敏感的问题
     * @param json 原始 JSON 字符串
     * @return canonical JSON（key 升序 + 无多余空格）
     * @throws Exception
     */
    private static String canonicalizeJson(String json) throws Exception {
        Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
        Map<String, Object> sortedMap = new TreeMap<>(map);
        return mapper.writeValueAsString(sortedMap);
    }

    /**
     * 对 JSON 数据进行 HMAC-SHA256 签名（canonical JSON）
     * @param jsonData  原始 JSON 字符串
     * @param secretKey 密钥（appSecret）
     * @return Base64 编码的签名字符串
     * @throws Exception
     */
    public static String sign(String jsonData, String secretKey) throws Exception {
        String canonicalJson = canonicalizeJson(jsonData);

        byte[] keyBytes = Base64.getDecoder().decode(secretKey);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(keySpec);
        byte[] rawHmac = mac.doFinal(canonicalJson.getBytes(StandardCharsets.UTF_8));

        return Base64.getEncoder().encodeToString(rawHmac);
    }

    /**
     * 验签方法
     * @param jsonData  原始 JSON 数据
     * @param signature Base64 编码的签名
     * @param secretKey Base64 编码的密钥
     * @return true 验签通过，false 验签失败
     * @throws Exception
     */
    public static boolean verify(String jsonData, String signature, String secretKey) throws Exception {
        String calcSign = sign(jsonData, secretKey);
        return calcSign.equals(signature);
    }

    /**
     * 检查请求时间戳是否在允许窗口内（毫秒级）
     *
     * @param timestampMillis 客户端传过来的 timestamp（毫秒）
     * @return true 表示有效，false 表示超时
     */
    public static boolean isTimestampValid(long timestampMillis) {
        long now = System.currentTimeMillis();
        long diff = Math.abs(now - timestampMillis);
        return diff <= timeWindowMs;
    }

}
